namespace Jsl.FxCop
{
    using System;
    using Microsoft.FxCop.Sdk;

    /// <summary>
    /// Warns about empty try structures.
    /// </summary>
    /// <remarks>
    /// If there is nothing in the try/catch then the catch will never execute and the whole try/catch should be removed.
    /// This rule must be run against assemblies build in debug mode since release mode will automatically remove all the
    /// empty try/catches.
    /// </remarks>
    public class RemoveEmptyTry : BaseIntrospectionRule
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="RemoveEmptyTry"/> class.
        /// </summary>
        public RemoveEmptyTry()
            : base(typeof(RemoveEmptyTry).Name, "Jsl.FxCop.Rules", typeof(RemoveEmptyTry).Assembly)
        {
        }
        #endregion Constructors

        #region Public Methods
        /// <summary>
        /// Checks the specified member.
        /// </summary>
        /// <param name="member">The member.</param>
        /// <returns>The problems found if any.</returns>
        public override ProblemCollection Check(Member member)
        {
            var method = member as Method;
            if (method != null)
            {
                this.Visit(method);
            }

            return this.Problems;
        }

        /// <summary>
        /// Visits the try.
        /// </summary>
        /// <param name="tryClause">The try clause.</param>
        public override void VisitTry(TryNode tryClause)
        {
            if (tryClause == null)
            {
                throw new ArgumentNullException("tryClause");
            }

            if (IsTryEmpty(tryClause))
            {
                this.Problems.Add(new Problem(this.GetResolution(), tryClause.SourceContext));
            }

            base.VisitTry(tryClause);
        }
        #endregion Public Methods

        #region Private Methods
        private static bool IsTryEmpty(TryNode tryClause)
        {
            foreach (var statement in tryClause.Block.Statements)
            {
                switch (statement.NodeType)
                {
                    case NodeType.Nop:
                        // Ignore no operation statements.
                        break;

                    case NodeType.Branch:
                        // Ignore the branch instruction that exits the try.
                        var branchStatement = (Branch)statement;
                        if (!branchStatement.LeavesExceptionBlock)
                        {
                            return false;
                        }

                        break;

                    default:
                        return false;
                }
            }

            return true;
        }
        #endregion Private Methods
    }
}